/*
 * $Id: mob_prog.c 933 2006-11-19 22:37:00Z zsuzsu $
 */

/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

/***************************************************************************
*	ROM 2.4 is copyright 1993-1995 Russ Taylor			   *
*	ROM has been brought to you by the ROM consortium		   *
*	    Russ Taylor (rtaylor@pacinfo.com)				   *
*	    Gabrielle Taylor (gtaylor@pacinfo.com)			   *
*	    Brian Moore (rom@rom.efn.org)				   *
*	By using this code, you have agreed to follow the terms of the	   *
*	ROM license, in the file Rom24/doc/rom.license			   *
***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  MOBprograms for ROM 2.4 v0.98g (C) M.Nylander 1996                     *
 *  Based on MERC 2.2 MOBprograms concept by N'Atas-ha.                    *
 *  Written and adapted to ROM 2.4 by                                      *
 *          Markku Nylander (markku.nylander@uta.fi)                       *
 *  This code may be copied and distributed as per the ROM license.        *
 *                                                                         *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <ctype.h>
#include <regex.h>

#include "merc.h"
#include "quest.h"
#include "mob_cmds.h"
#include "mob_prog.h"

/*
 * These defines correspond to the entries in fn_keyword[] table.
 * If you add a new if_check, you must also add a #define here.
 */
/* *INDENT-OFF* */
enum {
	CHK_RAND,
	CHK_MOBHERE,
	CHK_OBJHERE,
	CHK_MOBEXISTS,
	CHK_OBJEXISTS,
	CHK_PEOPLE,
	CHK_PLAYERS,
	CHK_MOBS,
	CHK_CLONES,
	CHK_ORDER,
	CHK_HOUR,
	CHK_ISPC,
	CHK_ISNPC,
	CHK_ISGOOD,
	CHK_ISEVIL,
	CHK_ISNEUTRAL,
	CHK_ISIMMORT,
	CHK_ISCHARM,
	CHK_ISPUMPED,
	CHK_ISFOLLOW,
	CHK_ISACTIVE,
	CHK_ISDELAY,
	CHK_ISVISIBLE,
	CHK_ISGHOST,
	CHK_HASTARGET,
	CHK_ISTARGET,
	CHK_EXISTS,
	CHK_AFFECTED,
	CHK_ACT,
	CHK_OFF,
	CHK_IMM,
	CHK_CARRIES,
	CHK_WEARS,
	CHK_HAS,
	CHK_USES,
	CHK_NAME,
	CHK_POS,
	CHK_CLAN,
	CHK_RACE,
	CHK_CLASS,
	CHK_OBJTYPE,
	CHK_VNUM,
	CHK_HPCNT,
	CHK_ROOM,
	CHK_SEX,
	CHK_LEVEL,
	CHK_ALIGN,
	CHK_MONEY,
	CHK_OBJVAL0,
	CHK_OBJVAL1,
	CHK_OBJVAL2,
	CHK_OBJVAL3,
	CHK_OBJVAL4,
	CHK_GRPSIZE,
	CHK_STR,
	CHK_INT,
	CHK_WIS,
	CHK_DEX,
	CHK_CON,
	CHK_CHA,
	CHK_WAIT,
	CHK_SAMECLAN,
        CHK_CLANLEAD,
        CHK_CLANSECOND,
	CHK_CLANCOMMONER,
	CHK_RELIGION,
	CHK_ISNEWBIE,
	CHK_ISONQUEST,
	CHK_ISQUESTCOMPLETE,
	CHK_HOMETOWN,
};

/*
 * These defines correspond to the entries in fn_evals[] table.
 */
#define EVAL_EQ            0
#define EVAL_GE            1
#define EVAL_LE            2
#define EVAL_GT            3
#define EVAL_LT            4
#define EVAL_NE            5

/*
 * if-check keywords:
 */
const char * fn_keyword[] =
{
    "rand",		/* if rand 30		- if random number < 30 */
    "mobhere",		/* if mobhere fido	- is there a 'fido' here */
    "objhere",		/* if objhere bottle	- is there a 'bottle' here */
			/* if mobhere 1233	- is there mob vnum 1233 here */
			/* if objhere 1233	- is there obj vnum 1233 here */
    "mobexists",	/* if mobexists fido	- is there a fido somewhere */
    "objexists",	/* if objexists sword	- is there a sword somewhere */

    "people",		/* if people > 4	- does room contain > 4 people */
    "players",		/* if players > 1	- does room contain > 1 pcs */
    "mobs",		/* if mobs > 2		- does room contain > 2 mobiles */
    "clones",		/* if clones > 3	- are there > 3 mobs of same vnum here */
    "order",		/* if order == 0	- is mob the first in room */
    "hour",		/* if hour > 11		- is the time > 11 o'clock */


    "ispc",		/* if ispc $n 		- is $n a pc */
    "isnpc",		/* if isnpc $n 		- is $n a mobile */
    "isgood",		/* if isgood $n 	- is $n good */
    "isevil",		/* if isevil $n 	- is $n evil */
    "isneutral",	/* if isneutral $n 	- is $n neutral */
    "isimmort",		/* if isimmort $n	- is $n immortal */
    "ischarm",		/* if ischarm $n	- is $n charmed */
    "ispumped",		/* if ispumped $n	- is $n pumped */
    "isfollow",		/* if isfollow $n	- is $n following someone */
    "isactive",		/* if isactive $n	- is $n's position > " is sleeping here." */
    "isdelay",		/* if isdelay $i	- does $i have mobprog pending */
    "isvisible",	/* if isvisible $n	- can mob see $n */
    "isghost",		/* if isghost $n	- is $n ghost */
    "hastarget",	/* if hastarget $i	- does $i have a valid target */
    "istarget",		/* if istarget $n	- is $n mob's target */
    "exists",		/* if exists $n		- does $n exist somewhere */

    "affected",		/* if affected $n blind - is $n affected by blind */
    "act",		/* if act $i sentinel	- is $i flagged sentinel */
    "off",              /* if off $i berserk	- is $i flagged berserk */
    "imm",              /* if imm $i fire	- is $i immune to fire */
    "carries",		/* if carries $n sword	- does $n have a 'sword' */
			/* if carries $n 1233	- does $n have obj vnum 1233 */
    "wears",		/* if wears $n lantern	- is $n wearing a 'lantern' */
			/* if wears $n 1233	- is $n wearing obj vnum 1233 */
    "has",    		/* if has $n weapon	- does $n have obj of type weapon */
    "uses",		/* if uses $n armor	- is $n wearing obj of type armor */
    "name",		/* if name $n puff	- is $n's name 'puff' */
    "pos",		/* if pos $n standing	- is $n standing */
    "clan",		/* if clan $n 'whatever'- does $n belong to clan 'whatever' */
    "race",		/* if race $n dragon	- is $n of 'dragon' race */
    "class",		/* if class $n mage	- is $n's class 'mage' */
    "objtype",		/* if objtype $p scroll	- is $p a scroll */

    "vnum",		/* if vnum $i == 1233  	- virtual number check */
    "hpcnt",		/* if hpcnt $i > 30	- hit point percent check */
    "room",		/* if room $i == 1233	- room virtual number */
    "sex",		/* if sex $i == 0	- sex check */
    "level",		/* if level $n < 5	- level check */
    "align",		/* if align $n < -1000	- alignment check */
    "money",		/* if money $n */
    "objval0",		/* if objval0 > 1000 	- object value[] checks 0..4 */
    "objval1",
    "objval2",
    "objval3",
    "objval4",
    "grpsize",		/* if grpsize $n > 6	- group size check */
    "str",
    "int",
    "wis",
    "dex",
    "con",
    "cha",
    "wait",
    "sameclan",
    "clanleader",
    "clansecond",
    "clancommoner",
    "religion",
    "isnewbie",		/* is under newbie protection */
    "isonquest",	/* is on a questor quest */
    "isquestcomplete",	/* has completed the quest */
    "hometown",		/* compare against the hometown of the character */


    "\n"		/* Table terminator */
};

const char *fn_evals[] =
{
    "==",
    ">=",
    "<=",
    ">",
    "<",
    "!=",
    "\n"
};

/*
 * Return a valid keyword from a keyword table
 */
int keyword_lookup(const char **table, char *keyword)
{
    register int i;
    for(i = 0; table[i][0] != '\n'; i++)
        if(!str_cmp(table[i], keyword))
            return(i);
    return -1;
}

/*
 * Perform numeric evaluation.
 * Called by cmd_eval()
 */
int num_eval(int lval, int oper, int rval)
{
    switch(oper)
    {
        case EVAL_EQ:
             return (lval == rval);
        case EVAL_GE:
             return (lval >= rval);
        case EVAL_LE:
             return (lval <= rval);
        case EVAL_NE:
             return (lval != rval);
        case EVAL_GT:
             return (lval > rval);
        case EVAL_LT:
             return (lval < rval);
        default:
             bug("num_eval: invalid oper", 0);
             return 0;
    }
}
/* *INDENT-ON* */
/*
 * ---------------------------------------------------------------------
 * UTILITY FUNCTIONS USED BY CMD_EVAL()
 * ----------------------------------------------------------------------
 */

/*
 * Get a random PC in the room (for $r parameter)
 */
CHAR_DATA *get_random_char(CHAR_DATA * mob)
{
	CHAR_DATA *vch, *victim = NULL;
	int now = 0, highest = 0;
	for (vch = mob->in_room->people; vch; vch = vch->next_in_room) {
		if (mob != vch && !IS_NPC(vch)
		    && can_see(mob, vch)
		    && (now = number_percent()) > highest) {
			victim = vch;
			highest = now;
		}
	}
	return victim;
}

/* 
 * How many other players / mobs are there in the room
 * iFlag: 0: all, 1: players, 2: mobiles 3: mobs w/ same vnum 4: same group
 */
int count_people_room(CHAR_DATA * mob, int iFlag)
{
	CHAR_DATA *vch;
	int count;
	for (count = 0, vch = mob->in_room->people; vch;
	     vch = vch->next_in_room)
		if (mob != vch && (iFlag == 0 || (iFlag == 1 && !IS_NPC(vch))
				   || (iFlag == 2 && IS_NPC(vch))
				   || (iFlag == 3 && IS_NPC(mob) && IS_NPC(vch)
				       && mob->pIndexData->vnum ==
				       vch->pIndexData->vnum)
				   || (iFlag == 4 && is_same_group(mob, vch)))
		    && can_see(mob, vch))
			count++;
	return (count);
}

/*
 * Get the order of a mob in the room. Useful when several mobs in
 * a room have the same trigger and you want only the first of them
 * to act 
 */
int get_order(CHAR_DATA * ch)
{
	CHAR_DATA *vch;
	int i;

	if (!IS_NPC(ch))
		return 0;
	for (i = 0, vch = ch->in_room->people; vch; vch = vch->next_in_room) {
		if (vch == ch)
			return i;
		if (IS_NPC(vch)
		    && vch->pIndexData->vnum == ch->pIndexData->vnum)
			i++;
	}
	return 0;
}


/*
 * Check if there's a mob with given vnum in the room
 */
bool get_mob_vnum_room(CHAR_DATA * ch, int vnum)
{
	CHAR_DATA *mob;
	for (mob = ch->in_room->people; mob; mob = mob->next_in_room)
		if (IS_NPC(mob) && mob->pIndexData->vnum == vnum)
			return TRUE;
	return FALSE;
}

/*
 * Check if there's an object with given vnum in the room
 */
bool get_obj_vnum_room(CHAR_DATA * ch, int vnum)
{
	OBJ_DATA *obj;
	for (obj = ch->in_room->contents; obj; obj = obj->next_content)
		if (obj->pIndexData->vnum == vnum)
			return TRUE;
	return FALSE;
}

/* ---------------------------------------------------------------------
 * CMD_EVAL
 * This monster evaluates an if/or/and statement
 * There are five kinds of statement:
 * 1) keyword and value (no $-code)	    if random 30
 * 2) keyword, comparison and value	    if people > 2
 * 3) keyword and actor		    	    if isnpc $n
 * 4) keyword, actor and value		    if carries $n sword
 * 5) keyword, actor, comparison and value  if level $n >= 10
 *
 *----------------------------------------------------------------------
 */
int cmd_eval(int vnum, const char *line, int check,
	     CHAR_DATA * mob, CHAR_DATA * ch,
	     const void *arg1, const void *arg2, CHAR_DATA * rch)
{
	CHAR_DATA *lval_char = mob;
	CHAR_DATA *vch = (CHAR_DATA *) arg2;
	OBJ_DATA *obj1 = (OBJ_DATA *) arg1;
	OBJ_DATA *obj2 = (OBJ_DATA *) arg2;
	OBJ_DATA *lval_obj = NULL;

	const char *original;
	char buf[MAX_INPUT_LENGTH], code;
	int lval = 0, oper = 0, rval = -1;

	original = line;
	line = one_argument(line, buf, sizeof(buf));
	if (buf[0] == '\0' || mob == NULL)
		return FALSE;

	/*
	 * If this mobile has no target, let's assume our victim is the one
	 */
	if (mob->mprog_target == NULL)
		mob->mprog_target = ch;

	switch (check) {
		/*
		 * Case 1: keyword and value
		 */
	case CHK_RAND:
		return (atoi(buf) < number_percent());
	case CHK_MOBHERE:
		if (is_number(buf))
			return (get_mob_vnum_room(mob, atoi(buf)));
		else
			return ((bool) (get_char_room(mob, buf) != NULL));
	case CHK_OBJHERE:
		if (is_number(buf))
			return (get_obj_vnum_room(mob, atoi(buf)));
		else
			return ((bool) (get_obj_here(mob, buf) != NULL));
	case CHK_MOBEXISTS:
		return ((bool) (get_char_world(mob, buf) != NULL));
	case CHK_OBJEXISTS:
		return ((bool) (get_obj_world(mob, buf) != NULL));
		/*
		 * Case 2 begins here: We sneakily use rval to indicate need
		 *                     for numeric eval...
		 */
	case CHK_PEOPLE:
		rval = count_people_room(mob, 0);
		break;
	case CHK_PLAYERS:
		rval = count_people_room(mob, 1);
		break;
	case CHK_MOBS:
		rval = count_people_room(mob, 2);
		break;
	case CHK_CLONES:
		rval = count_people_room(mob, 3);
		break;
	case CHK_ORDER:
		rval = get_order(mob);
		break;
	case CHK_HOUR:
		rval = time_info.hour;
		break;
	default:;
	}

	/*
	 * Case 2 continued: evaluate expression
	 */
	if (rval >= 0) {
		if ((oper = keyword_lookup(fn_evals, buf)) < 0) {
			builder_printf
			    ("cmd_eval: vnum %d: syntax error(2) '%s'", vnum,
			     original);
			return FALSE;
		}
		one_argument(line, buf, sizeof(buf));
		lval = rval;
		rval = atoi(buf);
		return (num_eval(lval, oper, rval));
	}

	/*
	 * Case 3,4,5: Grab actors from $* codes
	 */
	if (buf[0] != '$' || buf[1] == '\0') {
		builder_printf("cmd_eval: vnum %d: syntax error(3) '%s'",
			       vnum, original);
		return FALSE;
	} else
		code = buf[1];
	switch (code) {
	case 'i':
		lval_char = mob;
		break;
	case 'n':
		lval_char = ch;
		break;
	case 't':
		lval_char = vch;
		break;
	case 'r':
		lval_char = rch == NULL ? get_random_char(mob) : rch;
		break;
	case 'o':
		lval_obj = obj1;
		break;
	case 'p':
		lval_obj = obj2;
		break;
	case 'q':
		lval_char = mob->mprog_target;
		break;
	default:
		builder_printf("cmd_eval: vnum %d: syntax error(4) '%s'",
			       vnum, original);
		return FALSE;
	}
	/*
	 * From now on, we need an actor, so if none was found, bail out
	 */
	if (lval_char == NULL && lval_obj == NULL)
		return FALSE;

	/*
	 * Case 3: Keyword, comparison and value
	 */
	switch (check) {
	case CHK_ISPC:
		return (lval_char != NULL && !IS_NPC(lval_char));
	case CHK_ISNPC:
		return (lval_char != NULL && IS_NPC(lval_char));
	case CHK_ISNEWBIE:
		return (lval_char != NULL && IS_NEWBIE(lval_char));
	case CHK_ISONQUEST:
		return (lval_char != NULL && IS_ON_QUEST(lval_char));
	case CHK_ISQUESTCOMPLETE:
		return (lval_char != NULL && is_quest_complete(lval_char));
	case CHK_ISGOOD:
		return (lval_char != NULL && IS_GOOD(lval_char));
	case CHK_ISEVIL:
		return (lval_char != NULL && IS_EVIL(lval_char));
	case CHK_ISNEUTRAL:
		return (lval_char != NULL && IS_NEUTRAL(lval_char));
	case CHK_ISIMMORT:
		return (lval_char != NULL && IS_IMMORTAL(lval_char));
	case CHK_ISCHARM:	/* A relic from MERC 2.2 MOBprograms */
		return (lval_char != NULL && IS_AFFECTED(lval_char, AFF_CHARM));
	case CHK_ISPUMPED:
		return (lval_char != NULL && IS_PUMPED(lval_char));
	case CHK_ISFOLLOW:
		return (lval_char != NULL && lval_char->master != NULL
			&& lval_char->master->in_room == lval_char->in_room);
	case CHK_ISACTIVE:
		return (lval_char != NULL
			&& lval_char->position > POS_SLEEPING);
	case CHK_ISDELAY:
		return (lval_char != NULL && lval_char->mprog_delay > 0);
	case CHK_ISVISIBLE:
		switch (code) {
		default:
		case 'i':
		case 'n':
		case 't':
		case 'r':
		case 'q':
			return (lval_char != NULL && can_see(mob, lval_char));
		case 'o':
		case 'p':
			return (lval_obj != NULL && can_see_obj(mob, lval_obj));
		}
	case CHK_HASTARGET:
		return (lval_char != NULL && lval_char->mprog_target != NULL
			&& lval_char->in_room ==
			lval_char->mprog_target->in_room);
	case CHK_ISTARGET:
		return (lval_char != NULL && mob->mprog_target == lval_char);
	case CHK_ISGHOST:
		return (lval_char && IS_SET(lval_char->state_flags, STATE_GHOST));
	case CHK_WAIT:
		return (lval_char && lval_char->wait);
	case CHK_SAMECLAN:
		return (lval_char && lval_char->clan == mob->clan);
	case CHK_CLANLEAD:
		return (lval_char
			&& lval_char->pcdata->clan_status == CLAN_LEADER);
	case CHK_CLANSECOND:
		return (lval_char
			&& lval_char->pcdata->clan_status >= CLAN_MAGISTRATE);
	case CHK_CLANCOMMONER:
		return (lval_char
			&& lval_char->pcdata->clan_status < CLAN_MAGISTRATE);
	default:;
	}

	/* 
	 * Case 4: Keyword, actor and value
	 */
	line = one_argument(line, buf, sizeof(buf));
	switch (check) {
	case CHK_AFFECTED:
		return (lval_char != NULL
			&& IS_SET(lval_char->affected_by,
				  flag_value(affect_flags, buf)));
	case CHK_ACT:
		return (lval_char != NULL
			&& IS_SET(lval_char->pIndexData->act,
				  flag_value(act_flags, buf)));
	case CHK_IMM:
		return (lval_char != NULL
			&& IS_SET(lval_char->imm_flags,
				  flag_value(imm_flags, buf)));
	case CHK_OFF:
		return (lval_char != NULL
			&& IS_NPC(ch)
			&& IS_SET(lval_char->pIndexData->off_flags,
				  flag_value(off_flags, buf)));
	case CHK_CARRIES:
		if (is_number(buf))
			return (lval_char != NULL
				&& has_item(lval_char, atoi(buf), -1, FALSE));
		else
			return (lval_char != NULL
				&& (get_obj_carry(lval_char, buf) != NULL));
	case CHK_WEARS:
		if (is_number(buf))
			return (lval_char != NULL
				&& has_item(lval_char, atoi(buf), -1, TRUE));
		else
			return (lval_char != NULL
				&& (get_obj_wear(lval_char, buf) != NULL));
	case CHK_HAS:
		return (lval_char != NULL
			&& has_item(lval_char, -1, flag_value(item_types, buf),
				    FALSE));
	case CHK_USES:
		return (lval_char != NULL
			&& has_item(lval_char, -1, flag_value(item_types, buf),
				    TRUE));
	case CHK_NAME:
		switch (code) {
		default:
		case 'i':
		case 'n':
		case 't':
		case 'r':
		case 'q':
			return (lval_char != NULL
				&& is_name(buf, lval_char->name));
		case 'o':
		case 'p':
			return (lval_obj != NULL
				&& is_name(buf, lval_obj->name));
		}
	case CHK_POS:
		return (lval_char != NULL
			&& lval_char->position == flag_value(position_table,
							     buf));
	case CHK_CLAN:
		return (lval_char != NULL &&
			!str_cmp(clan_name(lval_char->clan), buf));
	case CHK_RACE:
		return (lval_char != NULL && lval_char->race == rn_lookup(buf));
	case CHK_CLASS:
		return (lval_char != NULL && !IS_NPC(lval_char)
			&& lval_char->class == cn_lookup(buf));
	case CHK_RELIGION:
		return (lval_char != NULL
			&& lval_char->religion == religion_lookup(buf));
	case CHK_HOMETOWN:
		return (lval_char != NULL
			&& lval_char->hometown == htn_lookup(buf));
	case CHK_OBJTYPE:
		return (lval_obj != NULL
			&& lval_obj->pIndexData->item_type ==
			flag_value(item_types, buf));
	default:;
	}

	/*
	 * Case 5: Keyword, actor, comparison and value
	 */
	if ((oper = keyword_lookup(fn_evals, buf)) < 0) {
		builder_printf("cmd_eval: vnum %d: syntax error(5): '%s'",
			       vnum, original);
		return FALSE;
	}
	one_argument(line, buf, sizeof(buf));
	rval = atoi(buf);

	switch (check) {
	case CHK_VNUM:
		switch (code) {
		default:
		case 'i':
		case 'n':
		case 't':
		case 'r':
		case 'q':
			if (lval_char != NULL && IS_NPC(lval_char))
				lval = lval_char->pIndexData->vnum;
			break;
		case 'o':
		case 'p':
			if (lval_obj != NULL)
				lval = lval_obj->pIndexData->vnum;
		}
		break;
	case CHK_HPCNT:
		if (lval_char != NULL)
			lval =
			    (lval_char->hit * 100) /
			    (UMAX(1, lval_char->max_hit));
		break;
	case CHK_ROOM:
		if (lval_char != NULL && lval_char->in_room != NULL)
			lval = lval_char->in_room->vnum;
		break;
	case CHK_SEX:
		if (lval_char != NULL)
			lval = lval_char->sex;
		break;
	case CHK_LEVEL:
		if (lval_char != NULL)
			lval = lval_char->level;
		break;
	case CHK_ALIGN:
		if (lval_char != NULL)
			lval = lval_char->alignment;
		break;
	case CHK_MONEY:	/* Money is converted to silver... */
		if (lval_char != NULL)
			lval = lval_char->gold + (lval_char->silver * 100);
		break;
	case CHK_OBJVAL0:
		if (lval_obj != NULL)
			lval = lval_obj->value[0];
		break;
	case CHK_OBJVAL1:
		if (lval_obj != NULL)
			lval = lval_obj->value[1];
		break;
	case CHK_OBJVAL2:
		if (lval_obj != NULL)
			lval = lval_obj->value[2];
		break;
	case CHK_OBJVAL3:
		if (lval_obj != NULL)
			lval = lval_obj->value[3];
		break;
	case CHK_OBJVAL4:
		if (lval_obj != NULL)
			lval = lval_obj->value[4];
		break;
	case CHK_GRPSIZE:
		if (lval_char != NULL)
			lval = count_people_room(lval_char, 4);
		break;
	case CHK_STR:
	case CHK_INT:
	case CHK_WIS:
	case CHK_DEX:
	case CHK_CON:
	case CHK_CHA:
		if (lval_char != NULL)
			lval = get_curr_stat(lval_char, check - CHK_STR);
		break;
	default:
		return FALSE;
	}
	return (num_eval(lval, oper, rval));
}

/*
 * ------------------------------------------------------------------------
 * EXPAND_ARG
 * This is a hack of act() in comm.c. I've added some safety guards,
 * so that missing or invalid $-codes do not crash the server
 * ------------------------------------------------------------------------
 */
void expand_arg(char *buf,
		const char *format,
		CHAR_DATA * mob, CHAR_DATA * ch,
		const void *arg1, const void *arg2, CHAR_DATA * rch)
{
	static char *const he_she[] = { "it", "he", "she" };
	static char *const him_her[] = { "it", "him", "her" };
	static char *const his_her[] = { "its", "his", "her" };
	const char *someone = "someone";
	const char *something = "something";
	const char *someones = "someone's";

	char fname[MAX_INPUT_LENGTH];
	CHAR_DATA *vch = (CHAR_DATA *) arg2;
	OBJ_DATA *obj1 = (OBJ_DATA *) arg1;
	OBJ_DATA *obj2 = (OBJ_DATA *) arg2;
	const char *str;
	const char *i;
	char *point;

	/*
	 * Discard null and zero-length messages.
	 */
	if (format == NULL || format[0] == '\0')
		return;

	point = buf;
	str = format;
	while (*str != '\0') {
		if (*str != '$') {
			*point++ = *str++;
			continue;
		}
		++str;

		switch (*str) {
		default:
			bug("Expand_arg: bad code %d.", *str);
			i = " <@@@> ";
			break;
		case 'i':
			one_argument(mob->name, fname, sizeof(fname));
			i = fname;
			break;
			/* XXX */
		case 'I':
			i = mlstr_mval(mob->short_descr);
			break;
		case 'n':
			i = someone;
			if (ch != NULL && can_see(mob, ch)) {
				one_argument(ch->name, fname, sizeof(fname));
				i = capitalize(fname);
			}
			break;
		case 'N':
			/* XXX */
			i = (ch != NULL && can_see(mob, ch))
			    ? (IS_NPC(ch) ? mlstr_mval(ch->short_descr) : ch->
			       name)
			    : someone;
			break;
		case 't':
			i = someone;
			if (vch != NULL && can_see(mob, vch)) {
				one_argument(vch->name, fname, sizeof(fname));
				i = capitalize(fname);
			}
			break;
		case 'T':
			/* XXX */
			i = (vch != NULL && can_see(mob, vch))
			    ? (IS_NPC(vch) ? mlstr_mval(vch->short_descr) :
			       vch->name)
			    : someone;
			break;
		case 'r':
			if (rch == NULL)
				rch = get_random_char(mob);
			i = someone;
			if (rch != NULL && can_see(mob, rch)) {
				one_argument(rch->name, fname, sizeof(fname));
				i = capitalize(fname);
			}
			break;
		case 'R':
			/* XXX */
			if (rch == NULL)
				rch = get_random_char(mob);
			i = (rch != NULL && can_see(mob, rch))
			    ? (IS_NPC(ch) ? mlstr_mval(ch->short_descr) : ch->
			       name)
			    : someone;
			break;
		case 'q':
			i = someone;
			if (mob->mprog_target != NULL
			    && can_see(mob, mob->mprog_target)) {
				one_argument(mob->mprog_target->name, fname,
					     sizeof(fname));
				i = capitalize(fname);
			}
			break;
		case 'Q':
			i = (mob->mprog_target != NULL &&
			     can_see(mob, mob->mprog_target)) ?
			    (IS_NPC(mob->mprog_target) ?
			     mlstr_mval(mob->mprog_target->short_descr) :
			     mob->mprog_target->name) : someone;
			break;
		case 'j':
			i = he_she[URANGE(0, mob->sex, 2)];
			break;
		case 'e':
			i = (ch != NULL && can_see(mob, ch))
			    ? he_she[URANGE(0, ch->sex, 2)]
			    : someone;
			break;
		case 'E':
			i = (vch != NULL && can_see(mob, vch))
			    ? he_she[URANGE(0, vch->sex, 2)]
			    : someone;
			break;
		case 'J':
			i = (rch != NULL && can_see(mob, rch))
			    ? he_she[URANGE(0, rch->sex, 2)]
			    : someone;
			break;
		case 'X':
			i = (mob->mprog_target != NULL
			     && can_see(mob, mob->mprog_target))
			    ? he_she[URANGE(0, mob->mprog_target->sex, 2)]
			    : someone;
			break;
		case 'k':
			i = him_her[URANGE(0, mob->sex, 2)];
			break;
		case 'm':
			i = (ch != NULL && can_see(mob, ch))
			    ? him_her[URANGE(0, ch->sex, 2)]
			    : someone;
			break;
		case 'M':
			i = (vch != NULL && can_see(mob, vch))
			    ? him_her[URANGE(0, vch->sex, 2)]
			    : someone;
			break;
		case 'K':
			if (rch == NULL)
				rch = get_random_char(mob);
			i = (rch != NULL && can_see(mob, rch))
			    ? him_her[URANGE(0, rch->sex, 2)]
			    : someone;
			break;
		case 'Y':
			i = (mob->mprog_target != NULL
			     && can_see(mob, mob->mprog_target))
			    ? him_her[URANGE(0, mob->mprog_target->sex, 2)]
			    : someone;
			break;
		case 'l':
			i = his_her[URANGE(0, mob->sex, 2)];
			break;
		case 's':
			i = (ch != NULL && can_see(mob, ch))
			    ? his_her[URANGE(0, ch->sex, 2)]
			    : someones;
			break;
		case 'S':
			i = (vch != NULL && can_see(mob, vch))
			    ? his_her[URANGE(0, vch->sex, 2)]
			    : someones;
			break;
		case 'L':
			if (rch == NULL)
				rch = get_random_char(mob);
			i = (rch != NULL && can_see(mob, rch))
			    ? his_her[URANGE(0, rch->sex, 2)]
			    : someones;
			break;
		case 'Z':
			i = (mob->mprog_target != NULL
			     && can_see(mob, mob->mprog_target))
			    ? his_her[URANGE(0, mob->mprog_target->sex, 2)]
			    : someones;
			break;
		case 'o':
			i = something;
			if (obj1 != NULL && can_see_obj(mob, obj1)) {
				one_argument(obj1->name, fname, sizeof(fname));
				i = fname;
			}
			break;
		case 'O':
			i = (obj1 != NULL && can_see_obj(mob, obj1))
			    ? mlstr_mval(obj1->short_descr)
			    : something;
			i = fix_short(i);
			break;
		case 'p':
			i = something;
			if (obj2 != NULL && can_see_obj(mob, obj2)) {
				one_argument(obj2->name, fname, sizeof(fname));
				i = fname;
			}
			break;
		case 'P':
			i = (obj2 != NULL && can_see_obj(mob, obj2))
			    ? mlstr_mval(obj2->short_descr)
			    : something;
			i = fix_short(i);
			break;
		}

		++str;
		while ((*point = *i) != '\0')
			++point, ++i;

	}
	*point = '\0';
}

/*
 * ------------------------------------------------------------------------
 *  PROGRAM_FLOW
 *  This is the program driver. It parses the mob program code lines
 *  and passes "executable" commands to interpret()
 *  Lines beginning with 'mob' are passed to mob_interpret() to handle
 *  special mob commands (in mob_cmds.c)
 *-------------------------------------------------------------------------
 */

#define MAX_NESTED_LEVEL 12	/* Maximum nested if-else-endif's (stack size) */
#define BEGIN_BLOCK       0	/* Flag: Begin of if-else-endif block */
#define IN_BLOCK         -1	/* Flag: Executable statements */
#define END_BLOCK        -2	/* Flag: End of if-else-endif block */
#define MAX_CALL_LEVEL    5	/* Maximum nested calls */

void program_flow(int pvnum, CHAR_DATA * mob, CHAR_DATA * ch,
		  const void *arg1, const void *arg2)
{
	CHAR_DATA *rch = NULL;
	const char *code, *line;
	char buf[MAX_STRING_LENGTH];
	char control[MAX_INPUT_LENGTH], data[MAX_STRING_LENGTH];
	MPCODE *mprog;

	static int call_level;	/* Keep track of nested "mpcall"s */

	int level, eval, check;
	int state[MAX_NESTED_LEVEL],	/* Block state (BEGIN,IN,END) */
	 cond[MAX_NESTED_LEVEL];	/* Boolean value based on the last if-check */

	if ((mprog = mpcode_lookup(pvnum)) == NULL) {
		builder_printf("program_flow: mob vnum %d: mprog vnum %d: "
			       "not found", mob->pIndexData->vnum, pvnum);
		return;
	}

	if (++call_level > MAX_CALL_LEVEL) {
		builder_printf("program_flow: vnum %d: MAX_CALL_LEVEL exceeded",
			       pvnum);
		goto bail_out;
	}

	/*
	 * Reset "stack"
	 */
	for (level = 0; level < MAX_NESTED_LEVEL; level++) {
		state[level] = IN_BLOCK;
		cond[level] = TRUE;
	}
	level = 0;

	code = mprog->code;
	/*
	 * Parse the MOBprog code
	 */
	while (*code) {
		bool first_arg = TRUE;
		char *b = buf, *c = control, *d = data;
		/*
		 * Get a command line. We sneakily get both the control word
		 * (if/and/or) and the rest of the line in one pass.
		 */
		while (isspace(*code) && *code)
			code++;
		while (*code) {
			if (*code == '\n' || *code == '\r')
				break;
			else if (isspace(*code)) {
				if (first_arg)
					first_arg = FALSE;
				else
					*d++ = *code;
			} else {
				if (first_arg)
					*c++ = *code;
				else
					*d++ = *code;
			}
			*b++ = *code++;
		}
		*b = *c = *d = '\0';

		if (buf[0] == '\0')
			break;
		if (buf[0] == '*')	/* Comment */
			continue;

		line = data;
		/* 
		 * Match control words
		 */
		if (!str_cmp(control, "if")) {
			if (state[level] == BEGIN_BLOCK) {
				builder_printf
				    ("program_flow: vnum %d: misplaced if statement",
				     pvnum);
				goto bail_out;
			}
			state[level] = BEGIN_BLOCK;
			if (++level >= MAX_NESTED_LEVEL) {
				builder_printf
				    ("program_flow: vnum %d: max nested level exceeded",
				     pvnum);
				goto bail_out;
			}
			if (level && cond[level - 1] == FALSE) {
				cond[level] = FALSE;
				continue;
			}
			line = one_argument(line, control, sizeof(control));
			if ((check = keyword_lookup(fn_keyword, control)) >= 0) {
				cond[level] =
				    cmd_eval(pvnum, line, check, mob, ch, arg1,
					     arg2, rch);
			} else {
				builder_printf
				    ("program_flow: vnum %d: invalid if_check (if)",
				     pvnum);
				goto bail_out;
			}
			state[level] = END_BLOCK;
		} else if (!str_cmp(control, "or")) {
			if (!level || state[level - 1] != BEGIN_BLOCK) {
				builder_printf
				    ("program_flow: vnum %d: 'or' without 'if'",
				     pvnum);
				goto bail_out;
			}
			if (level && cond[level - 1] == FALSE)
				continue;
			line = one_argument(line, control, sizeof(control));
			if ((check = keyword_lookup(fn_keyword, control)) >= 0) {
				eval =
				    cmd_eval(pvnum, line, check, mob, ch, arg1,
					     arg2, rch);
			} else {
				builder_printf
				    ("program_flow: vnum %d: invalid if_check (or)",
				     pvnum);
				goto bail_out;
			}
			cond[level] = (eval == TRUE) ? TRUE : cond[level];
		} else if (!str_cmp(control, "and")) {
			if (!level || state[level - 1] != BEGIN_BLOCK) {
				builder_printf
				    ("program_flow: vnum %d: 'and' without 'if'",
				     pvnum);
				goto bail_out;
			}
			if (level && cond[level - 1] == FALSE)
				continue;
			line = one_argument(line, control, sizeof(control));
			if ((check = keyword_lookup(fn_keyword, control)) >= 0) {
				eval =
				    cmd_eval(pvnum, line, check, mob, ch, arg1,
					     arg2, rch);
			} else {
				builder_printf
				    ("program_flow: vnum %d: invalid if_check (and)",
				     pvnum);
				goto bail_out;
			}
			cond[level] = (cond[level] == TRUE)
			    && (eval == TRUE) ? TRUE : FALSE;
		} else if (!str_cmp(control, "endif")) {
			if (!level || state[level - 1] != BEGIN_BLOCK) {
				builder_printf
				    ("program_flow: vnum %d: 'endif' without 'if'",
				     pvnum);
				goto bail_out;
			}
			cond[level] = TRUE;
			state[level] = IN_BLOCK;
			state[--level] = END_BLOCK;
		} else if (!str_cmp(control, "else")) {
			if (!level || state[level - 1] != BEGIN_BLOCK) {
				builder_printf
				    ("program_flow: vnum %d: 'else' without 'if'",
				     pvnum);
				goto bail_out;
			}
			if (level && cond[level - 1] == FALSE)
				continue;
			state[level] = IN_BLOCK;
			cond[level] = (cond[level] == TRUE) ? FALSE : TRUE;
		} else if (cond[level] == TRUE
			   && (!str_cmp(control, "break")
			       || !str_cmp(control, "end")))
			goto bail_out;
		else if ((!level || cond[level] == TRUE) && buf[0] != '\0') {
			state[level] = IN_BLOCK;
			expand_arg(data, buf, mob, ch, arg1, arg2, rch);
			if (!str_cmp(control, "mob")) {
				/* 
				 * Found a mob restricted command, pass it to mob interpreter
				 */
				line =
				    one_argument(data, control,
						 sizeof(control));
				mob_interpret(mob, line);
			} else {
				/* 
				 * Found a normal mud command, pass it to interpreter
				 */
				interpret(mob, data);
			}
		}
	}

      bail_out:
	call_level--;
}

/* 
 * ---------------------------------------------------------------------
 * Trigger handlers. These are called from various parts of the code
 * when an event is triggered.
 * ---------------------------------------------------------------------
 */

/*
 * A general purpose string trigger. Matches argument to a string trigger
 * phrase.
 */
void mp_act_trigger(const char *argument, CHAR_DATA * mob, CHAR_DATA * ch,
		    const void *arg1, const void *arg2, int type)
{
	MPTRIG *mptrig;
	char *l = strlwr(argument);

	for (mptrig = mob->pIndexData->mptrig_list; mptrig;
	     mptrig = mptrig->next) {
		bool match;

		if (mptrig->type != type)
			continue;

		match = FALSE;
		if (IS_SET(mptrig->flags, TRIG_REGEXP))
			match = !regexec(mptrig->extra, argument, 0, NULL, 0);
		else if (strstr(IS_SET(mptrig->flags, TRIG_CASEDEP) ?
				argument : l, mptrig->phrase))
			match = TRUE;

		if (match) {
			program_flow(mptrig->vnum, mob, ch, arg1, arg2);
			break;
		}
	}
}

/*
 * A general purpose percentage trigger. Checks if a random percentage
 * number is less than trigger phrase
 */
bool mp_percent_trigger(CHAR_DATA * mob, CHAR_DATA * ch,
			const void *arg1, const void *arg2, int type)
{
	MPTRIG *prg;

	for (prg = mob->pIndexData->mptrig_list; prg != NULL; prg = prg->next) {
		if (prg->type == type && number_percent() < atoi(prg->phrase)) {
			program_flow(prg->vnum, mob, ch, arg1, arg2);
			return (TRUE);
		}
	}
	return (FALSE);
}

void mp_bribe_trigger(CHAR_DATA * mob, CHAR_DATA * ch, int amount)
{
	MPTRIG *prg;

	/*
	 * Original MERC 2.2 MOBprograms used to create a money object
	 * and give it to the mobile. WFT was that? Funcs in act_obj()
	 * handle it just fine.
	 */
	for (prg = mob->pIndexData->mptrig_list; prg; prg = prg->next) {
		if (prg->type == TRIG_BRIBE && amount >= atoi(prg->phrase)) {
			program_flow(prg->vnum, mob, ch, NULL, NULL);
			break;
		}
	}
	return;
}

bool mp_exit_trigger(CHAR_DATA * ch, int dir)
{
	CHAR_DATA *mob;
	MPTRIG *prg;

	for (mob = ch->in_room->people; mob != NULL; mob = mob->next_in_room) {
		if (IS_NPC(mob)
		    && (HAS_TRIGGER(mob, TRIG_EXIT)
			|| HAS_TRIGGER(mob, TRIG_EXALL))) {
			for (prg = mob->pIndexData->mptrig_list; prg;
			     prg = prg->next) {
				/*
				 * Exit trigger works only if the mobile is not busy
				 * (fighting etc.). If you want to be sure all players
				 * are caught, use ExAll trigger
				 */
				if (prg->type == TRIG_EXIT
				    && dir == atoi(prg->phrase)
				    && mob->position ==
				    mob->pIndexData->default_pos
				    && can_see(mob, ch)) {
					program_flow(prg->vnum, mob, ch, NULL,
						     NULL);
					return TRUE;
				} else
				    if (prg->type == TRIG_EXALL
					&& dir == atoi(prg->phrase)) {
					program_flow(prg->vnum, mob, ch, NULL,
						     NULL);
					return TRUE;
				}
			}
		}
	}
	return FALSE;
}

void mp_give_trigger(CHAR_DATA * mob, CHAR_DATA * ch, OBJ_DATA * obj)
{
	char buf[MAX_INPUT_LENGTH];
	const char *p;
	MPTRIG *prg;

	for (prg = mob->pIndexData->mptrig_list; prg; prg = prg->next)
		if (prg->type == TRIG_GIVE) {
			p = prg->phrase;
			/*
			 * Vnum argument
			 */
			if (is_number(p)) {
				if (obj->pIndexData->vnum == atoi(p)) {
					program_flow(prg->vnum, mob, ch,
						     (void *) obj, NULL);
					return;
				}
			}
			/*
			 * Object name argument, e.g. 'sword'
			 */
			else {
				while (*p) {
					p = one_argument(p, buf, sizeof(buf));

					if (is_name(buf, obj->name)
					    || !str_cmp("all", buf)) {
						program_flow(prg->vnum, mob, ch,
							     (void *) obj,
							     NULL);
						return;
					}
				}
			}
		}
}

void mp_greet_trigger(CHAR_DATA * ch)
{
	CHAR_DATA *mob;

	for (mob = ch->in_room->people; mob != NULL; mob = mob->next_in_room) {
		if (IS_NPC(mob)
		    && (HAS_TRIGGER(mob, TRIG_GREET)
			|| HAS_TRIGGER(mob, TRIG_GRALL))) {
			/*
			 * Greet trigger works only if the mobile is not busy
			 * (fighting etc.). If you want to catch all players, use
			 * GrAll trigger
			 */
			if (HAS_TRIGGER(mob, TRIG_GREET)
			    && mob->position == mob->pIndexData->default_pos
			    && can_see(mob, ch))
				mp_percent_trigger(mob, ch, NULL, NULL,
						   TRIG_GREET);
			else if (HAS_TRIGGER(mob, TRIG_GRALL))
				mp_percent_trigger(mob, ch, NULL, NULL,
						   TRIG_GRALL);
		}
	}
	return;
}

void mp_hprct_trigger(CHAR_DATA * mob, CHAR_DATA * ch)
{
	MPTRIG *prg;

	for (prg = mob->pIndexData->mptrig_list; prg != NULL; prg = prg->next)
		if ((prg->type == TRIG_HPCNT)
		    && ((100 * mob->hit / mob->max_hit) < atoi(prg->phrase))) {
			program_flow(prg->vnum, mob, ch, NULL, NULL);
			break;
		}
}
